package repair

import (
	"common/encryp/rs_coder"
	rsEnc "common/encryp/rs_coder"
	"common/pb"
	"common/rpc"
	"common/rs"
	"context"
	"euplink/center_rpc"
	"euplink/download/ecClient"
	"euplink/stream"
	"euplink/upload/upClient"
	"fmt"
	"go.uber.org/zap"
	"time"
)

type repair struct {
	logger        *zap.Logger
	dialer        *rpc.TcpDialer
	index         int //segment index
	streamId      []byte
	downloadNodes []center_rpc.NodeInfo //修复的数据源
	uploadNodes   []center_rpc.NodeInfo //需要上传的节点
	expireTime    time.Time
	rs            rs.Rs
	padding       int
}

func NewRepairer(logger *zap.Logger, streamId []byte, index, padding int, expireTime time.Time, rs rs.Rs,
	dialer *rpc.TcpDialer, downloadNodes, uploadNodes []center_rpc.NodeInfo) *repair {
	return &repair{
		logger:        logger,
		dialer:        dialer,
		index:         index,
		streamId:      streamId,
		downloadNodes: downloadNodes,
		uploadNodes:   uploadNodes,
		expireTime:    expireTime,
		rs:            rs,
		padding:       padding,
	}
}
func (r *repair) Run(ctx context.Context) ([]*pb.PieceStoreInfo, error) {
	pieces, downloadSuccessNodes, err := r.downloadRepairSegment(ctx)
	if err != nil {
		r.logger.Error("downloadRepairSegment error", zap.Error(err))
		return nil, err
	}
	uploadResult, err := r.uploadRepairPieces(ctx, pieces)
	if err != nil {
		r.logger.Error("uploadRepairPieces error ", zap.Error(err))
		return nil, err
	}
	//把uploadResult 与下载的节点合并, 得到一个新的数据存储节点集合
	newPieceStoreInfo := make([]*pb.PieceStoreInfo, 0)
	for _, result := range uploadResult {
		newPieceStoreInfo = append(newPieceStoreInfo, &pb.PieceStoreInfo{
			Index:  int32(result.Num),
			NodeId: result.NodeId.Bytes(),
		})
	}
	for _, node := range r.downloadNodes {
		var isDownloadSuccss = false
		for _, downloadSuccessNode := range downloadSuccessNodes {
			if node.DevId == downloadSuccessNode {
				isDownloadSuccss = true
			}
		}
		if isDownloadSuccss {
			newPieceStoreInfo = append(newPieceStoreInfo, &pb.PieceStoreInfo{
				Index:  int32(node.PieceNum),
				NodeId: node.NodeId.Bytes(),
			})
		}
	}
	r.logger.Info("修复结果", zap.Int("len", len(newPieceStoreInfo)))
	return newPieceStoreInfo, nil
}

// downloadRepairSegment 传入 N 个节点。N >= 最小修复数。
func (r *repair) downloadRepairSegment(ctx context.Context) (*rsEnc.Pieces, []string, error) {
	var SegmentSize = 0
	var logger = r.logger
	var padding = r.padding

	downloadClient := ecClient.NewClient(r.index, logger, r.streamId, SegmentSize, r.dialer, &r.rs, nil)
	results, err := downloadClient.Download(ctx, r.downloadNodes)
	if err != nil {
		logger.Error("DownloadSegments", zap.Error(err))
		return nil, nil, err
	}
	pieceData := &rs_coder.Pieces{
		Shared:     make(map[int][]byte),
		PaddingLen: padding,
	}
	var nodesExistPiece []string
	for _, result := range results {
		if result.Err == nil {
			pieceData.Shared[result.Num] = result.Data
			nodesExistPiece = append(nodesExistPiece, result.DevId)
		}
	}

	//解码数据
	repairPieces, err := stream.ReRS(pieceData, r.rs)
	//repairPieces 排除掉已存在的piece
	for _, result := range results {
		delete(repairPieces.Shared, result.Num)
	}

	return repairPieces, nodesExistPiece, err
}

// uploadRepairPieces 输入数据与需要上传的节点。节点数 > （total-Repair）
func (r *repair) uploadRepairPieces(ctx context.Context, pieceData *rsEnc.Pieces) ([]upClient.PieceUploadResult, error) {
	//pieceData.Shared 已排除好的节点的 piece
	//计算最少上传数
	var minUploadCount = len(pieceData.Shared) - int(r.rs.Total-r.rs.MinUpload)
	if minUploadCount <= 0 || minUploadCount > int(r.rs.Total) {
		return nil, fmt.Errorf("error uploadcount %d", minUploadCount)
	}

	//这里不是全部上传，只上传必要的节点。因为有些节点已有 piece 数据
	//返回的节点可能比较多
	if len(r.uploadNodes) > len(pieceData.Shared) {
		r.uploadNodes = r.uploadNodes[0:len(pieceData.Shared)]
	}

	client := upClient.NewClient(r.index, r.logger, r.streamId, minUploadCount, r.expireTime, r.dialer, nil)
	result, uploadErr := client.Upload(ctx, pieceData, r.uploadNodes)
	if uploadErr != nil {
		r.logger.Error("client.Upload", zap.Error(uploadErr))
	}

	return result, uploadErr
}
